﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Common.Utilities;
using Common.Utilities.Dispatcher;

namespace Workflows.Components
{
    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class ForStatement
    {
        private string _VarName;
        /// <summary></summary>
        public string VarName
        {
            get
            {
                return this._VarName;
            }
            set
            {
                this._VarName = value;
            }
        }

        private SimpleDataType _DataType;
        /// <summary></summary>
        public SimpleDataType DataType
        {
            get
            {
                return this._DataType;
            }
            set
            {
                this._DataType = value;
            }
        }

        private object _FromValue;
        /// <summary></summary>
        public object FromValue
        {
            get
            {
                return this._FromValue;
            }
            set
            {
                this._FromValue = value;
            }
        }

        private object _ToValue;
        /// <summary></summary>
        public object ToValue
        {
            get
            {
                return this._ToValue;
            }
            set
            {
                this._ToValue = value;
            }
        }

        private object _Step;
        /// <summary></summary>
        public object Step
        {
            get
            {
                return this._Step;
            }
            set
            {
                this._Step = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="loopTimes"></param>
        public ForStatement(int loopTimes)
            : this("i", SimpleDataType.IntNumberType, 1, loopTimes, 1)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="varName"></param>
        /// <param name="dataType"></param>
        /// <param name="fromValue"></param>
        /// <param name="toValue"></param>
        /// <param name="step"></param>
        public ForStatement(string varName, SimpleDataType dataType, 
            object fromValue, object toValue, object step)
        {
            this._VarName = varName;
            this._DataType = dataType;
            this._FromValue = fromValue;
            this._ToValue = toValue;
            this._Step = step;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class ForLoopInstruction:InstructionBase
    {
        #region props
        private ForStatement _ForExpression;
        /// <summary>
        /// var name
        /// var type
        /// var init
        /// var evaluate
        /// var increment
        /// </summary>
        public ForStatement ForExpression
        {
            get
            {
                return this._ForExpression;
            }
            set
            {
                this._ForExpression = value;
            }
        }

        private Dictionary<string,GlobalVariable> _Vars;
        /// <summary></summary>
        public Dictionary<string,GlobalVariable> Vars
        {
            get
            {
                return this._Vars;
            }
            set
            {
                this._Vars = value;
            }
        }

        private string _InitCodeBlock;
        /// <summary></summary>
        public string InitCodeBlock
        {
            get
            {
                return this._InitCodeBlock;
            }
            set
            {
                this._InitCodeBlock = value;
            }
        }

        private string _ConsoleAppFilePath;
        /// <summary></summary>
        public string ConsoleAppFilePath
        {
            get
            {
                return this._ConsoleAppFilePath;
            }
            set
            {
                this._ConsoleAppFilePath = value;
            }
        }

        private Dictionary<string,string> _SubflowVarBindings;
        /// <summary></summary>
        public Dictionary<string,string> SubflowVarBindings
        {
            get
            {
                return this._SubflowVarBindings;
            }
            set
            {
                this._SubflowVarBindings = value;
            }
        }
        #endregion

        #region ctor
        /// <summary>
        /// 
        /// </summary>
        public ForLoopInstruction():base(Guid.Empty, Guid.Empty)
        {
            this._ForExpression=new ForStatement(1);
            this._Vars=new Dictionary<string, GlobalVariable>();
            this._InitCodeBlock = string.Empty;
            this._ConsoleAppFilePath = string.Empty;
            this._SubflowVarBindings=new Dictionary<string, string>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="componentID"></param>
        /// <param name="jobTicketID"></param>
        /// <param name="forStatement"></param>
        /// <param name="gVars"></param>
        /// <param name="initCode"></param>
        /// <param name="consoleAppPath"></param>
        /// <param name="subflowVarBindings"></param>
        public ForLoopInstruction(Guid componentID, Guid jobTicketID,
            ForStatement forStatement,
            Dictionary<string,GlobalVariable> gVars,
            string initCode, string consoleAppPath,
            Dictionary<string,string> subflowVarBindings) 
            : base(componentID, jobTicketID)
        {
            this._ForExpression = forStatement;
            this._Vars = gVars;
            this._InitCodeBlock = initCode;
            this._ConsoleAppFilePath = consoleAppPath;
            this._SubflowVarBindings = subflowVarBindings;
        }
        #endregion

        #region Overrides of InstructionBase

        /// <summary>
        /// serialize settings to xml
        /// the function below is mainly for storing into database as serialized form instead of string
        /// </summary>
        public override XmlNode Serialize(ref XmlDocument xDoc, ref XmlNode parentNode)
        {
            XmlNode xNode = XmlDataUtil.AddElement(ref xDoc, parentNode, "Instruction");
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "Type", this.GetType().FullName);
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "ComponentID", this.OwnerComponentID.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "WorkerID", this.OwnerWorkerInstanceID.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "SubmitTime", this.SubmitTime.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "StartTime", this.StartTime.ToString());

            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "ForVarName", this.ForExpression.VarName);
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "ForVarDataType", this.ForExpression.DataType.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "FromValue", this.ForExpression.FromValue.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "ToValue", this.ForExpression.ToValue.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "Step", this.ForExpression.Step.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "InitCodeBlock", this.InitCodeBlock);
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "ConsoleAppFilePath", this.ConsoleAppFilePath);
            XmlNode gVarNodes = XmlDataUtil.AddElement(ref xDoc, xNode, "GlobalVariables");
            if(this.Vars !=null && this.Vars.Count>0)
            {
                foreach(string varName in this.Vars.Keys)
                {
                    XmlNode gVarNode = XmlDataUtil.AddElement(ref xDoc, gVarNodes, "GlobalVariable");
                    XmlDataUtil.UpdateAttribute(ref xDoc, gVarNode,"VarName",varName);
                    XmlDataUtil.UpdateAttribute(ref xDoc, gVarNode, "DataType", this.Vars[varName].DataType.ToString());
                    XmlDataUtil.UpdateAttribute(ref xDoc, gVarNode, "Value", this.Vars[varName].VarValue);
                }
            }
            XmlNode bindingNodes = XmlDataUtil.AddElement(ref xDoc, xNode, "SubflowVarBindings");
            if(this.SubflowVarBindings !=null && this.SubflowVarBindings.Count>0)
            {
                foreach(string tgtVarName in this.SubflowVarBindings.Keys)
                {
                    XmlNode bindingNode = XmlDataUtil.AddElement(ref xDoc, bindingNodes, "SubflowVarBinding");
                    XmlDataUtil.UpdateAttribute(ref xDoc, bindingNode, "SubflowVarName",tgtVarName);
                    XmlDataUtil.UpdateAttribute(ref xDoc, bindingNode, "SrcVarName",this.SubflowVarBindings[tgtVarName]);
                }
            }

            return xNode;
        }

        /// <summary>
        /// instantiated from xml
        /// "&lt;Instructions&gt;&lt;Instruction&gt;&lt;/Instruction&gt;&lt;/Instruction&gt;", must
        /// instantiate from first child of root node
        /// </summary>
        public override InstructionBase Instantiate(XmlNode xNode)
        {
            if (xNode.Attributes.GetNamedItem("Type").Value == this.GetType().FullName)
            {
                ForLoopInstruction instruction = new ForLoopInstruction();
                instruction.OwnerComponentID = new Guid(xNode.Attributes.GetNamedItem("ComponentID").Value);
                instruction.OwnerWorkerInstanceID = new Guid(xNode.Attributes.GetNamedItem("WorkerID").Value);
                instruction.SubmitTime = DateTime.Parse(xNode.Attributes.GetNamedItem("SubmitTime").Value);
                instruction.StartTime = DateTime.Parse(xNode.Attributes.GetNamedItem("StartTime").Value);

                string forVarName = XmlDataUtil.GetAttributeValue(xNode, "ForVarName", "");
                SimpleDataType forVarDataType =
                    (SimpleDataType)
                    Enum.Parse(typeof (SimpleDataType),
                               XmlDataUtil.GetAttributeValue(xNode, "ForVarDataType",
                                                             SimpleDataType.IntNumberType.ToString()));
                object fromValue = SimpleDataTypeUtil.ToDbValue(
                    forVarDataType, XmlDataUtil.GetAttributeValue(xNode, "FromValue", "1"));
                object toValue = SimpleDataTypeUtil.ToDbValue(
                    forVarDataType, XmlDataUtil.GetAttributeValue(xNode, "ToValue", "1"));
                object step = SimpleDataTypeUtil.ToDbValue(
                    forVarDataType, XmlDataUtil.GetAttributeValue(xNode, "Step", "1"));
                instruction.ForExpression=new ForStatement(forVarName,forVarDataType,fromValue, toValue, step);
                instruction.InitCodeBlock = XmlDataUtil.GetAttributeValue(xNode, "InitCodeBlock", "");
                instruction.ConsoleAppFilePath = XmlDataUtil.GetAttributeValue(xNode, "ConsoleAppFilePath", "");
                instruction.Vars=new Dictionary<string, GlobalVariable>();
                XmlNodeList varNodes = xNode.SelectNodes("GlobalVariables/GlobalVariable");
                if(varNodes !=null && varNodes.Count>0)
                {
                    foreach(XmlNode varNode in varNodes)
                    {
                        string varName = XmlDataUtil.GetAttributeValue(varNode, "VarName", "");
                        SimpleDataType dataType =
                            (SimpleDataType)
                            Enum.Parse(typeof (SimpleDataType),
                                       XmlDataUtil.GetAttributeValue(varNode, "DataType",
                                                                     SimpleDataType.TextType.ToString()));
                        string varValue = XmlDataUtil.GetAttributeValue(varNode, "Value", "");
                        instruction.Vars.Add(varName, new GlobalVariable(varName,dataType, varValue));
                    }
                }
                instruction.SubflowVarBindings=new Dictionary<string, string>();
                XmlNodeList bindingNodes = xNode.SelectNodes("SubflowVarBindings/SubflowVarBinding");
                if(bindingNodes !=null && bindingNodes.Count>0)
                {
                    foreach(XmlNode bindingNode in bindingNodes)
                    {
                        string srcVarName = XmlDataUtil.GetAttributeValue(bindingNode, "SrcVarName", "");
                        string tgtVarName = XmlDataUtil.GetAttributeValue(bindingNode, "SubflowVarName", "");
                        instruction.SubflowVarBindings.Add(tgtVarName,srcVarName);
                    }
                }

                return instruction;
            }
            else
            {
                throw new Exception("Unable to instantiate instruction: invalid type specified in xml node");
            }
        }

        public override Dictionary<string, Type> GetBindableMemberNames(Type[] excludeMemberFromTypes)
        {
            Dictionary<string,Type> members = base.GetBindableMemberNames(excludeMemberFromTypes);
            members.Add("From",typeof(int));
            members.Add("To", typeof(int));
            foreach(GlobalVariable gVar in this.Vars.Values)
            {
                Type gVarType = SimpleDataTypeUtil.ToSystemType(gVar.DataType);
                if(members.ContainsKey(gVar.VarName))
                    members[gVar.VarName] = gVarType;
                else 
                    members.Add(gVar.VarName, gVarType);
            }
            return members;
        }

        public override object ReadMemberValue(string memberName)
        {
            if (this.Vars != null && this.Vars.ContainsKey(memberName))
                return SimpleDataTypeUtil.ToDbValue(this.Vars[memberName].DataType, this.Vars[memberName].VarValue);
            else if(memberName=="From")
                return this.ForExpression.FromValue;
            else if(memberName=="To")
                return this.ForExpression.ToValue;
            return base.ReadMemberValue(memberName);
        }

        public override void SetMemberValue(string memberName, object memberValue)
        {
            if (this.Vars != null && this.Vars.ContainsKey(memberName))
            {
                if (memberValue == null)
                    this.Vars[memberName].VarValue = null;
                else
                    this.Vars[memberName].VarName = memberValue.ToString();
            }
            else if (memberName == "From")
                this.ForExpression.FromValue = (int) memberValue;
            else if (memberName == "To")
                this.ForExpression.ToValue = (int) memberValue;
            else
                base.SetMemberValue(memberName, memberValue);
        }

        #endregion
    }
}
